package com.xiao.user.service;

import com.xiao.user.exception.EntityNotFoundException;
import com.xiao.user.mapper.EntityMapper;
import com.xiao.user.model.DisableAuditingEntity;
import com.xiao.user.model.RemovableEntity;
import com.xiao.user.repository.BaseRepository;
import com.xiao.user.service.criteria.Criteria;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;

import static java.util.stream.Collectors.toList;


@Slf4j
public abstract class CurdService<E, D, K> {

    private final BaseRepository<E, K> repository;
    private final EntityMapper<D, E> entityMapper;

    @Autowired
    private EntityManager entityManager;

    protected CurdService(BaseRepository<E, K> repository,
                          EntityMapper<D, E> mapper) {
        this.repository = repository;
        this.entityMapper = mapper;
    }

    protected EntityMapper<D, E> getEntityMapper() {
        return entityMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    public D create(D dto) {
        E entity = entityMapper.toEntity(dto);
        entity = beforeUpdate(entity);
        entity = repository.save(entity);
        entity = afterUpdate(entity);
        return entityMapper.toFullDto(entity);
    }

    @Transactional(rollbackFor = Exception.class)
    public D update(K id, D dto) {
//        E entity = entityMapper.toEntity(dto);
        E entity = repository.findById(id)
                .orElseThrow(() -> new EntityNotFoundException(getActualTypeArgument().getSimpleName(), id));
        entityMapper.partialUpdate(entity, dto);
        entity = beforeUpdate(entity);
        entity = repository.save(entity);
        entity = afterUpdate(entity);
        return entityMapper.toFullDto(entity);
    }

    @Transactional(rollbackFor = Exception.class)
    public void uploadBatch(List<D> dtos) {
        List<E> entities = entityMapper.toEntity(dtos);
        repository.saveAll(entities.stream().map(this::beforeUpdate).collect(toList()));
        entities.forEach(this::afterUpdate);
    }

    protected E beforeUpdate(E entity) {
        return entity;
    }

    protected E afterUpdate(E entity) {
        return entity;
    }

    @Transactional(readOnly = true)
    public Optional<D> findById(K id) {
        return repository.findById(id).map(entityMapper::toFullDto);
    }

    @Transactional(readOnly = true)
    public List<D> findByIds(List<K> ids) {
        if (ids == null || ids.size() == 0 || ids.stream().anyMatch(id -> id == null)) {
            log.error("非法参数，id不能为空。ids={}", ids);
            return new ArrayList<>();
        }

        return ids.stream()
                .map(id -> repository.findById(id))
                .map(entity -> entity.orElse(null))
                .filter(entity -> entity != null)
                .map(entityMapper::toDto)
                .collect(toList());
    }

    @Transactional(readOnly = true)
    public Page<D> findAll(Pageable pageable) {
        return repository.findAll(pageable).map(entityMapper::toDto);
    }

    @Transactional(readOnly = true)
    public List<D> findAll() {
        return StreamSupport.stream(repository.findAll().spliterator(), true)
                .map(entityMapper::toDto)
                .collect(toList());
    }

    @Transactional(readOnly = true)
    public Page<D> findByCriteria(Criteria criteria, Pageable pageable) {
        final Specification<E> specification = createSpecification(criteria);
        return repository
                .findAll(specification, pageable)
                .map(entityMapper::toDto);
    }

    @Transactional(readOnly = true)
    public List<D> findByCriteria(Criteria criteria) {
        final Specification<E> specification = createSpecification(criteria);
        return repository
                .findAll(specification)
                .stream()
                .map(entityMapper::toDto)
                .collect(toList());
    }

    protected <R> Page<R> findAllGroupedBy(Class<R> returnClass,
                                           Criteria criteria,
                                           SelectionSpecification<E> selectionSpecification,
                                           GroupSpecification<E> groupSpecification,
                                           Pageable pageable) {

        /**调用entityManager.getCriteriaBuilder()来获取CriteriaBuilder,
         * CriteriaBuilder可以用于创建CriteriaQuery、CriteriaUpdate和CriteriaDelete。
         *除此之外类似count、max等函数也是由CriteriaBuilder来创建的。
         * 其中Entitymanager可以使用@PersistenceContext注解来进行注入
         */
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

        /** 调用criteriaBuilder.createQuery来创建CriteriaQuery
         *  其中createQuery的参数是Query返回值类型
         */
        CriteriaQuery<R> criteriaQuery = criteriaBuilder.createQuery(returnClass);

        // 设置查询条件
        //参数是对应于表的实体类，criteriaQuery.from类似于sql中的from语句，该方法的执行等价于sql中的from 表名(这里是实体类)。
        Root<E> root = criteriaQuery.from(getActualTypeArgument());
        Specification<E> specification = createSpecification(criteria);
        Predicate predicate = specification.toPredicate(root, criteriaQuery, criteriaBuilder);
        if (predicate != null) {
            criteriaQuery.where(predicate);
        }

        // 设置group by字段.
        criteriaQuery.groupBy(groupSpecification.build(root, criteriaBuilder));

       // 设置读取的字段
        criteriaQuery.multiselect(selectionSpecification.build(root, criteriaBuilder));

        // 设置order by
        if (pageable != null) {
            pageable.getSort();
            criteriaQuery.orderBy(getOrderBy(root, criteriaBuilder, pageable));
        }


        // 设置分页页面
        TypedQuery<R> query = entityManager.createQuery(criteriaQuery);
        int total = query.getResultList().size();
        if (pageable != null && pageable.isPaged()) {
            query.setFirstResult((int) pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());
        }

        // 执行分页查询
        assert pageable != null;
        return new PageImpl<>(
                query.getResultList(),
                pageable,
                total);
    }

    protected <R> List<R> findAllGroupedBy(Class<R> returnClass,
                                           Criteria criteria,
                                           SelectionSpecification<E> selectionSpecification,
                                           GroupSpecification<E> groupSpecification,
                                           Sort sort) {

        /**调用entityManager.getCriteriaBuilder()来获取CriteriaBuilder,
         * CriteriaBuilder可以用于创建CriteriaQuery、CriteriaUpdate和CriteriaDelete。
         *除此之外类似count、max等函数也是由CriteriaBuilder来创建的。
         * 其中Entitymanager可以使用@PersistenceContext注解来进行注入
         */
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

        /** 调用criteriaBuilder.createQuery来创建CriteriaQuery
         *  其中createQuery的参数是Query返回值类型
         */
        CriteriaQuery<R> criteriaQuery = criteriaBuilder.createQuery(returnClass);

        // 设置查询条件
        //参数是对应于order表的实体类，criteriaQuery.from类似于sql中的from语句，该方法的执行等价于sql中的from 表名(这里是实体类)。
        Root<E> root = criteriaQuery.from(getActualTypeArgument());
        Specification<E> specification = createSpecification(criteria);
        Predicate predicate = specification.toPredicate(root, criteriaQuery, criteriaBuilder);
        if (predicate != null) {
            criteriaQuery.where(predicate);
        }

        // 设置读取的字段
        criteriaQuery.multiselect(selectionSpecification.build(root, criteriaBuilder));

        // 设置group by字段.
        criteriaQuery.groupBy(groupSpecification.build(root, criteriaBuilder));

        // 设置order by
        // TODO:

        // 设置分页页面
        TypedQuery<R> query = entityManager.createQuery(criteriaQuery);
        return query.getResultList();
    }


    public Class<E> getActualTypeArgument() {
        Class<E> entityClass = null;
        //获取该类型的直接父类,如果该类型没有直接父类，那么返回null
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        //判断genericSuperclass是否被实例化
        if (genericSuperclass instanceof ParameterizedType) {
            //在ParameterizedType中有一个方法:GetActualTypeArguments()这个方法的返回值是一个Type的数组里面存的就是我们的要的T
            Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass)
                    .getActualTypeArguments();
            if (actualTypeArguments != null && actualTypeArguments.length > 0) {
                entityClass = (Class<E>) actualTypeArguments[0];
            }
        }
        //返回一个实例化的bean
        return entityClass;
    }

    protected List<Order> getOrderBy(final Root<E> root, final CriteriaBuilder qb, final Pageable pageable) {
        final List<Order> orderBy = new ArrayList<Order>();
        for (final Sort.Order order : pageable.getSort()) {
            if (order.isAscending()) {
                orderBy.add(qb.asc(root.get(order.getProperty())));
            } else {
                orderBy.add(qb.desc(root.get(order.getProperty())));
            }
        }
        return orderBy;
    }

    protected Specification<E> createSpecification(Criteria criteria) {
        return Specification.where(null);
    }

    @Transactional(rollbackFor = Exception.class)
    public void delete(K id) {
        final Optional<E> optionalEntity = repository.findById(id);

        optionalEntity.ifPresent(entity -> {
            if (entity instanceof DisableAuditingEntity) {
                final DisableAuditingEntity disabledEntity = (DisableAuditingEntity) entity;
                if (Boolean.TRUE.equals(disabledEntity.getDisabled())) {
                    repository.deleteById(id);
                } else {
                    disabledEntity.setDisabled(true);
                    repository.save(entity);
                }
            } else if (entity instanceof RemovableEntity) {
                final RemovableEntity removableEntity = (RemovableEntity) entity;
                if (Boolean.TRUE.equals(removableEntity.getDeleted())) {
                    repository.deleteById(id);
                } else {
                    removableEntity.setDeleted(true);
                    repository.save(entity);
                }
            } else {
                repository.deleteById(id);
            }
        });
    }

    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(K[] ids) {
        Arrays.stream(ids).forEach(this::delete);
    }
}
